// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SimpleModbusTCPTests.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Test.Robotics.Hardware.Modbus
{
    using System;
    using Microsoft.Robotics.Hardware.Modbus;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Tests for the SimpleModbusTCPConnection class
    /// Example operations/packets taken from Robotiq S Model manual as indicated
    /// </summary>
    [TestClass]
    public class SimpleModbusTCPTests
    {
        /// <summary>
        /// Grip completed packet, read all registers example from pg. 71 of Robotiq S Model manual
        /// </summary>
        private byte[] gripCompletedPacket = 
        {
            0x77,
            0x6B,
            0x00,
            0x00,
            0x00,
            0x13,
            0x02,
            0x04,
            0x10,
            0xB9,
            0xEA,
            0x00,
            0xFF,
            0xBC,
            0x00,
            0x00,
            0xC1,
            0x00,
            0x00,
            0xBD,
            0x00,
            0x00,
            0x89,
            0x00,
            0x00
        };

        /// <summary>
        /// These are flipped from above due to big endian byte in ModbusTCP packet.
        /// </summary>
        private byte[] gripCompletedTransactionID = { 0x6B, 0x77 };

        /// <summary>
        /// Expected registers extracted from grip completed packet above 
        /// </summary>
        private ushort[] gripCompletedExpectedRegisters = 
        {
            0xEAB9,
            0xFF00,
            0x00BC,
            0xC100,
            0x0000,
            0x00BD,
            0x8900,
            0x0000
        };

        /// <summary>
        /// Read status request example from pg. 70 of Robotiq manual
        /// </summary>
        private byte[] readStatusPacket = 
        {
            0x77,
            0x6B,
            0x00,
            0x00,
            0x00,
            0x06,
            0x02,
            0x04,
            0x07,
            0xD0,
            0x00,
            0x08
        };
           
        /// <summary>
        /// will recreate activation request from pg. 66 of Robotiq S Model user manual and confirm
        /// we get expected byte sequence.  Will correct start address to 0x0000 (example in manual
        /// uses 0x03E8 which is for ModbusRTU not ModbusTCP
        /// </summary>
        private byte[] activationRequestPacket = 
        {
            0x33,
            0x9A,
            0x00,
            0x00,
            0x00,
            0x0D,
            0x02,
            0x10,
            0x00,
            0x00,
            0x00,
            0x03,
            0x06,
            0x01,
            0x00,
            0x00,
            0x00,
            0x00,
            0x00
        };

        /// <summary>
        /// Address of first register to write to for activation request.
        /// Note that value of 0x03E8 given on pg. 66 of Robotiq S Model manual is incorrect.
        /// </summary>
        private ushort activationRequestAddressOfFirstRegister = 0x0000;

        /// <summary>
        /// Number of words to write for activation request.
        /// </summary>
        private ushort activationRequestWordsToWrite = 0x03;

        /// <summary>
        /// Register values for activation request packet
        /// </summary>
        private ushort[] activationRequestRegisterValues = { 0x0001, 0x0000, 0x0000 };

        /// <summary>
        /// UnitID to use for tests.
        /// </summary>
        private byte unitID = 0x02;

        /// <summary>
        /// Address of first register to read when reading all registers
        /// </summary>
        private ushort readAllRegistersAddressOfFirstRegister = 0x07D0;

        /// <summary>
        /// Number of words to read when reading all registers
        /// </summary>
        private ushort readAllRegistersWordsToRead = 8;

        /// <summary>
        /// Test that extracting registers from the response to a Read Input Registers
        /// message works.
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        public void ParseReadInputRegistersResponseTest()
        {
            ushort[] registers;

            ReadInputRegistersPacket.ParseReadInputRegistersResponse(this.gripCompletedTransactionID, this.gripCompletedPacket, out registers);

            for (int i = 0; i < 8; i++)
            {
                Assert.AreEqual(this.gripCompletedExpectedRegisters[i], registers[i], "Byte " + i + " had unexpected value");
            }
        }

        /// <summary>
        /// Test that extracting registers from the response to a Read Input Registers
        /// message works.
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        public void ReadInputRegistersTest()
        {
            SimulatedSimpleModbusTCPConnection modbusConnection = new SimulatedSimpleModbusTCPConnection();

            modbusConnection.ExpectedSendPacket = this.readStatusPacket;

            ushort[] registers;

            // In the simulated connection class, an exception will be thrown if this command generates unexpected
            // packet values.
            modbusConnection.ReadInputRegisters(
                this.unitID,
                this.readAllRegistersAddressOfFirstRegister,
                this.readAllRegistersWordsToRead,
                out registers);

            modbusConnection.Dispose();
        }

        /// <summary>
        /// Test that building a Read Input Registers request packet works.
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        public void BuildReadInputRegistersRequestTest()
        {
            // byte orders are flipped because ModbusTCP is big endian byte order.
            byte[] buffer = new byte[256];
            byte[] transactionID = { 0x6B, 0x77 };

            ReadInputRegistersPacket.BuildReadInputRegistersRequest(
                this.unitID,
                this.readAllRegistersAddressOfFirstRegister,
                this.readAllRegistersWordsToRead,
                transactionID,
                buffer);

            for (int i = 0; i < this.readAllRegistersWordsToRead; ++i)
            {
                Assert.AreEqual(this.readStatusPacket[i], buffer[i], "Byte " + i + " had unexpected value");
            }
        }

        /// <summary>
        /// Test <c>PresetMultipleRegisters</c> results in expected packet being sent
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        public void PresetMultipleRegistersTest()
        {
            SimulatedSimpleModbusTCPConnection modbusConnection = new SimulatedSimpleModbusTCPConnection();

            modbusConnection.ExpectedSendPacket = this.activationRequestPacket;

            // In the simulated connection class, an exception will be thrown if this command generates unexpected
            // packet values.
            modbusConnection.PresetMultipleRegisters(
                this.unitID,
                this.activationRequestAddressOfFirstRegister,
                this.activationRequestWordsToWrite,
                this.activationRequestRegisterValues);

            modbusConnection.Dispose();
        }
 
        /// <summary>
        /// Test that preset register packets are built correctly
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        public void BuildPresetMultipleRegistersRequestTest()
        {
            byte[] actualByteSequence = new byte[256];

            byte[] transactionID = { 0x9A, 0x33 };

            int bytesRead = PresetMultipleRegistersPacket.BuildPresetMultipleRegistersRequest(
                this.unitID,
                this.activationRequestAddressOfFirstRegister,
                this.activationRequestWordsToWrite,
                transactionID,
                this.activationRequestRegisterValues,
                actualByteSequence);

            Assert.AreEqual(this.activationRequestPacket.Length, bytesRead, "Read the expected number of bytes");

            for (int i = 0; i < bytesRead; ++i)
            {
                Assert.AreEqual(this.activationRequestPacket[i], actualByteSequence[i], "Byte " + i + " has expected value");
            }
        }

        /// <summary>
        /// Test that a complete packet is properly identified
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        public void IsCompletePacketTest()
        {
            // check that a complete packet is properly identified
            Assert.IsTrue(
                ModbusTCPPacket.IsCompletePacket(this.gripCompletedPacket, this.gripCompletedPacket.Length),
                "Complete packet correctly identified");

            // check that an incomplete packet is properly identified
            // just need to decrement length for this as assumption is made that not all space in provided buffer is used.
            Assert.IsFalse(
                ModbusTCPPacket.IsCompletePacket(this.gripCompletedPacket, this.gripCompletedPacket.Length - 1),
                "Incomplete packet correctly identified");

            // also test case where not enough bytes indicated to determine rest of packet length
            Assert.IsFalse(
                ModbusTCPPacket.IsCompletePacket(this.gripCompletedPacket, 5),
                "Packet with invalid length specification properly identified");
        }

        /// <summary>
        /// Test that <c>ParseReadInputRegistersResponse</c> throws if 1st byte is incorrect
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        [ExpectedException(typeof(ApplicationException))]
        public void ParseReadInputRegistersResponse1stByteIncorrect()
        {
            byte[] corruptedReadInputRegistersResponse = new byte[this.gripCompletedPacket.Length];
            ushort[] registers;

            this.gripCompletedPacket.CopyTo(corruptedReadInputRegistersResponse, 0);

            corruptedReadInputRegistersResponse[0] = 0;

            ReadInputRegistersPacket.ParseReadInputRegistersResponse(this.gripCompletedTransactionID, corruptedReadInputRegistersResponse, out registers);
        }

        /// <summary>
        /// Test that <c>ParseReadInputRegistersResponse</c> throws if 2nd byte is incorrect
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        [ExpectedException(typeof(ApplicationException))]
        public void ParseReadInputRegistersResponse2ndByteIncorrect()
        {
            byte[] corruptedReadInputRegistersResponse = new byte[this.gripCompletedPacket.Length];
            ushort[] registers;

            this.gripCompletedPacket.CopyTo(corruptedReadInputRegistersResponse, 0);

            corruptedReadInputRegistersResponse[1] = 0;

            ReadInputRegistersPacket.ParseReadInputRegistersResponse(this.gripCompletedTransactionID, corruptedReadInputRegistersResponse, out registers);
        }

        /// <summary>
        /// Test that <c>ParseReadInputRegistersResponse</c> throws if 3rd byte is incorrect
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        [ExpectedException(typeof(ApplicationException))]
        public void ParseReadInputRegistersResponse3rdByteIncorrect()
        {
            byte[] corruptedReadInputRegistersResponse = new byte[this.gripCompletedPacket.Length];
            ushort[] registers;

            this.gripCompletedPacket.CopyTo(corruptedReadInputRegistersResponse, 0);

            corruptedReadInputRegistersResponse[2] = 1;

            ReadInputRegistersPacket.ParseReadInputRegistersResponse(this.gripCompletedTransactionID, corruptedReadInputRegistersResponse, out registers);
        }

        /// <summary>
        /// Test that <c>ParseReadInputRegistersResponse</c> throws if 4th byte is incorrect
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        [ExpectedException(typeof(ApplicationException))]
        public void ParseReadInputRegistersResponse4thByteIncorrect()
        {
            byte[] corruptedReadInputRegistersResponse = new byte[this.gripCompletedPacket.Length];
            ushort[] registers;

            this.gripCompletedPacket.CopyTo(corruptedReadInputRegistersResponse, 0);

            corruptedReadInputRegistersResponse[3] = 1;

            ReadInputRegistersPacket.ParseReadInputRegistersResponse(this.gripCompletedTransactionID, corruptedReadInputRegistersResponse, out registers);
        }

        /// <summary>
        /// Test that <c>ParseReadInputRegistersResponse</c> throws if function number is incorrect
        /// </summary>
        [TestMethod]
        [TestCategory("Unit")]
        [Priority(0)]
        [ExpectedException(typeof(ApplicationException))]
        public void ParseReadInputRegistersResponseFunctionNumberByteIncorrect()
        {
            byte[] corruptedReadInputRegistersResponse = new byte[this.gripCompletedPacket.Length];
            ushort[] registers;

            this.gripCompletedPacket.CopyTo(corruptedReadInputRegistersResponse, 0);

            corruptedReadInputRegistersResponse[7] = 0;

            ReadInputRegistersPacket.ParseReadInputRegistersResponse(this.gripCompletedTransactionID, corruptedReadInputRegistersResponse, out registers);
        }
    }
}
